#ifndef MESSAGE_H_INCLUDED
#define MESSAGE_H_INCLUDED

#include <iostream>
#include <string>
#include <list>
#include <boost/fusion/container/vector.hpp>
#include <boost/fusion/view/joint_view.hpp>
#include <boost/fusion/functional.hpp>
#include <boost/fusion/algorithm/iteration.hpp>
#include <boost/algorithm/string/replace.hpp>
#include <boost/format.hpp>
#include <boost/lambda/lambda.hpp>
#include <boost/ref.hpp>
#include <boost/function.hpp>
#include <boost/lexical_cast.hpp>
using namespace boost::lambda;

template<unsigned int> class MessageSequences;
template<unsigned int> class Message;
template<unsigned int> class MessageException;
class MessageBase;
class MessageQueue;
class MessageQueueMark;

const std::string INFO("info");
const std::string ERROR("error");
const std::string WARNING("warning");
const std::string FATAL_ERROR("fatal_error");

#define DECL_MSG(_type, _name, _format, ...) \
template<> const std::string Message< _name >::name = # _name; \
template<> const std::string Message< _name >::type = _type; \
template<> const std::string Message< _name >::format = std::string("In function %s at %s:%d : ") + _type + std::string(" %d : ") + std::string( _format ); \
template<> boost::fusion::unfused_typed< boost::function< Message< _name >::MsgException (const Message< _name >::Seq &) >, Message< _name>::Seq> Message< _name >::_pushMessage( Message< _name >::private_pushMessage );



#define DEF_MSG(id, _name, ...)                 \
const unsigned int _name = id;                  \
template<> class MessageSequences< id > { \
public:                                         \
    typedef boost::fusion::vector< const char*, const char*, unsigned int, unsigned int, __VA_ARGS__ > Seq;   \
};

class MessageBase
{
protected :
    friend class MessageQueueMark;
    friend class MessageQueue;
    template<unsigned int id> friend class Message;
    template<unsigned int id> friend class MessageException;

    MessageBase* next;
    MessageBase* previous;
public:
    virtual unsigned int getId() = 0;
    virtual std::string getStr() = 0;

    bool operator==(unsigned int id)
    {
        return getId() == id;
    }

    virtual ~MessageBase()
    {
    }
};

template<unsigned int id> class Message : public MessageBase
{
private:
    friend class MessageQueue;

    typedef typename MessageSequences<id>::Seq Seq;
    typedef class MessageException<id> MsgException;

    static MessageException<id> private_pushMessage(const Seq & _s);
    static const std::string name;
    static const std::string type;
    static const std::string format;

    Seq s;
    class MessageQueue* queue;

    Message(Seq _s) : s(_s)
    {
    }

    Message(const Message &);
    Message & operator=( const Message &);
public:
    static boost::fusion::unfused_typed< boost::function< MsgException (const Seq &) >, Seq> _pushMessage;

    std::string getStr();
    std::string getCompactStr();
    void print();

    unsigned int getId()
    {
        return id;
    }

    Seq & getArgs()
    {
        return s;
    }

    bool operator==(unsigned int _id)
    {
        return id == _id;
    }

    ~Message();
};



template<unsigned int id> class MessageException
{
private:
    MessageQueue* queue;
    Message<id>* msg;

public:
    MessageQueue & getQueue()
    {
        return queue;
    }

    Message<id>* getMessage()
    {
        return msg;
    }

    MessageException(MessageQueue* _queue, Message<id>* _msg)
    {
        queue = _queue;
        msg = _msg;
    }

    ~MessageException()
    {
    }
};

class MessageQueueMark
{
private:
    template<unsigned int> friend class MessageException;
    template<unsigned int> friend class Message;
    friend class MessageQueue;

    bool mClear;

    MessageBase* mFirst; //First message recorded
    MessageBase* mLast; //Last message

    MessageBase* mCurrent; //Current message

    MessageQueue* mQueue;
public:
    MessageQueueMark(MessageQueue &queue);
    ~MessageQueueMark()
    {
    }

    MessageBase* next(bool clear = false);
    MessageBase* last();
    MessageBase* get();
    void clear();

    void printMessage();
    void printAll();
    void clearAll();
};


class MessageQueue
{
private:
    friend class MessageQueueMark;
    template<unsigned int> friend class Message;
    std::list<MessageQueueMark*> marks;
    MessageBase* last;

    template<unsigned int id> MessageException<id> pushMessage(Message<id>* message);

public:

    MessageQueue()
    {
        last = NULL;
    }



    static MessageQueue & getInstance()
    {
        static MessageQueue inst;
        return inst;
    }
};

#define messageQueueInst MessageQueue::getInstance()


template<unsigned int id> Message<id>::~Message()
{
    for( std::list<MessageQueueMark*>::iterator iter = queue->marks.begin(); iter != queue->marks.end(); iter++)
    {
        if( this == (*iter)->mLast)
            (*iter)->mLast = next;

        if( this == (*iter)->mFirst)
            (*iter)->mFirst = previous;
    }

    if( this == queue->last)
        queue->last = next;

    if( previous)
        previous->next = next;

    if( next)
        next->previous = previous;
}


template<unsigned int id> MessageException<id> Message<id>::private_pushMessage(const Seq & _s)
{
    return messageQueueInst.pushMessage(new Message(_s));
}

template<unsigned int id> std::string Message<id>::getStr()
{
    boost::format fmt( format);
    boost::fusion::for_each(s, boost::lambda::ret<boost::format>(boost::ref(fmt) % _1));
    return str(fmt);
}

template<unsigned int id> std::string Message<id>::getCompactStr()
{
    std::ostringstream res;
    res << "#" << boost::lexical_cast<std::string>(id);
    boost::fusion::for_each(s, boost::ref(res) += std::string("# #") + bind( (std::string (*)(std::string, std::string, std::string))(boost::replace_all_copy) ,lexical_cast<std::string>(_1), "#", "##"));
    res << "#";
    return res.str();
}

template<unsigned int id> void Message<id>::print()
{
    wxLogMessage(wxString(getStr().c_str(), wxConvUTF8));
}

MessageQueueMark::MessageQueueMark(MessageQueue & queue)
{
    mQueue = &queue;
    queue.marks.push_front(this);
    mFirst = NULL;
    mLast = NULL;
    mCurrent = NULL;

    mClear = false;
}

MessageBase* MessageQueueMark::next(bool clear)
{
    if( clear)
        mClear = true;

    MessageBase* tmp = mCurrent;

    if( mCurrent == mFirst)
        mCurrent = NULL;
    else
        mCurrent = mCurrent->next;

    if( mClear)
        delete tmp;
    mClear = false;

    return mCurrent;
}

MessageBase* MessageQueueMark::last()
{
    mCurrent = mLast;
    return mLast;
}

MessageBase* MessageQueueMark::get()
{
    return mCurrent;
}

void MessageQueueMark::clear()
{
    mClear = true;
}

void MessageQueueMark::printMessage()
{
    if( mCurrent)
        std::cout << mCurrent->getStr();
}

void MessageQueueMark::printAll()
{
    MessageBase* msg = mLast;

    if( !msg)
        return;

    while( msg != mFirst->next)
    {
        std::cout << msg->getStr() << std::endl;
        msg = msg->next;
    }
}

void MessageQueueMark::clearAll()
{
    if( !mLast)
        return;

    MessageBase* tmp;
    MessageBase* msg = mLast;

    MessageBase* firstNext = mFirst->next;
    while( msg != firstNext)
    {
        tmp = msg;
        msg = msg->next;

        delete tmp;
    }

    mFirst = NULL; mLast = NULL; mCurrent = NULL;
}


template<unsigned int id> MessageException<id> MessageQueue::pushMessage(Message<id>* message)
{
    message->next = last;
    message->previous = NULL;
    message->queue = this;

    if( last)
        last->previous = message;

    last = message;

    for( std::list<MessageQueueMark*>::iterator iter = marks.begin(); iter != marks.end(); iter++)
    {
        (*iter)->mLast = last;
        (*iter)->mCurrent = last;
        if( !(*iter)->mFirst)
            (*iter)->mFirst = last;
    }

    return MessageException<id>(this, message);
}

#ifdef __GNUG__
#define pushMessage(id, ...) Message< id >::_pushMessage( __PRETTY_FUNCTION__, __FILE__, __LINE__, id,  __VA_ARGS__)
#elif _MSC_VER
#define pushMessage(id, ...) Message< id >::_pushMessage( __FUNCSIG__, __FILE__, __LINE__, id, __VA_ARGS__)
#else
#error pushMessage function name handling error
#endif

#include "messages_def.h"

#endif // MESSAGE_H_INCLUDED
